#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
transhtml.py - HTML文件翻译脚本

该脚本读取HTML文件，提取其中的可翻译文本内容，使用DashScope的qwen-mt-turbo模型进行翻译，
并生成结构一致但内容已翻译的新HTML文件。翻译过程保留原始HTML结构、标签和格式。

使用方式：
python transhtml.py -i input.html

生成的翻译后文件名为：input-trans.html

安全特性：
- API密钥从环境变量获取，避免硬编码
- 详细的错误处理和日志记录
- 输入验证和输出确认
- 防止处理过大的文件
- 请求频率控制，避免API限流

生产环境准备：
- 完整的中文注释
- 异常处理
- 性能监控
- 用户友好的输出信息
- 频率限制和重试机制
"""

import os
import sys
import argparse
from bs4 import BeautifulSoup, NavigableString
import dashscope
import time
import logging
from typing import Dict, List, Optional
import random


# 配置日志记录
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)


def get_translation_options() -> Dict:
    """
    获取翻译配置选项
    
    返回一个包含翻译配置的字典，包括源语言、目标语言、翻译领域要求、
    翻译记忆列表和术语表等配置项。
    
    Returns:
        dict: 包含翻译配置的字典
    """
    return {
        # 源语言设置为英文
        "source_lang": "English",
        # 目标语言设置为中文
        "target_lang": "Chinese",
        # 翻译风格和质量要求，指定翻译应具备优美流畅、适合播客的特性
        "domains": '请将此文本译为优美流畅、适合播客的中文，文字需优雅动听、节奏分明，便于口语表达。语气应温暖而权威，如智者分享洞见，兼具诗意雅韵与思想深度。善用比喻、排比及四字成语（如"思想之河"、"智慧火花"），以增强韵律与意象；避免专业术语，以通俗易懂的语言确保清晰。按照中文自然语义单元重组句子，保证听觉清晰与逻辑流畅。保留原文修辞华彩与情感细腻，关键术语翻译需统一且令人难忘。结尾应余音绕梁，唤起惊奇与期待。整体效果宛如一场优雅的心灵对话——启迪人心，引人入胜，听之愉悦。',
        # 预定义翻译记忆，包含特定句子的翻译对，确保一致性
        "tm_list": [
            {"source": "Some bacteria have a clever method of defending themselves against viral attack - a system that essentially provides them with immunity against viruses.","target": "一些细菌拥有一种巧妙的方法来抵御病毒攻击——这种系统本质上赋予了它们对病毒的免疫力。"},
            {"source": "When bacteria are exposed to viruses they copy a section of the viral genetic code into their own genome.","target": "当细菌暴露于病毒时，会将病毒基因组中的一段序列复制到自己的基因组中。"},
            {"source": "It seems foolish, aiding and abetting the virus in this way, but it's not.","target": "这样做看似愚蠢，仿佛是在协助病毒，实则不然。"},
            {"source": "The piece of pathogen DNA is flanked by strange, repeating sections of genetic code: bookmarks for the bacterium.","target": "这段病原体DNA两侧是奇特的重复基因序列：它们就像是细菌的书签。"},
            {"source": "It's these bookmarks that are known as CRISPR: Clustered Regularly Interspaced Short Palindromic Repeats.","target": "这些“书签”就是所谓的CRISPR：成簇规律间隔短回文重复序列。"},
            {"source": "The RNA guide homes in and locks onto DNA arriving with an invading pathogen - and the enzyme neatly cuts it up, disabling it.","target": "RNA向导会精准定位并结合入侵病原体带来的DNA——随后酶将其整齐地剪断，使其失效。"},
            {"source": "You can make as many cuts as you like, where you like.","target": "你可以随心所欲地在任意位置进行任意多次切割。"},
            {"source": "With this new gene-editing technology, it's possible to snip out particular genes much more precisely than ever before.","target": "借助这项新的基因编辑技术，人们能够以前所未有的精确度剪除特定基因。"},
            {"source": "CRISPR could also be used therapeutically - to remove damaged DNA in living organisms.","target": "CRISPR也可用于治疗目的——清除活体生物中受损的DNA。"},
            {"source": "It's already been employed, in the lab, to remove cancer-causing pieces of viral DNA from human cells.","target": "它已在实验室中被用于从人类细胞中移除致癌的病毒DNA片段。"},
            {"source": "CRISPR makes it possible to precisely remove a section of DNA and to splice in another.","target": "CRISPR使得人们能够精确地切除一段DNA，并插入另一段。"},
            {"source": "We're really only just beginning to understand the conversations that go on between our genes, our hormones, and our environment.","target": "我们才刚刚开始理解基因、激素与环境之间复杂的“对话”。"},
        ],
        # 术语表，确保关键术语的翻译一致性
        "terms": [
            {"source": "aurochs", "target": "原牛"},
            {"source": "ancient ecosystem", "target": "古代生态系统"},
            {"source": "Ancient DNA", "target": "古代DNA"},
            {"source": "Ana Poets", "target": "安娜·波茨"},
            {"source": "apple", "target": "苹果树"},
            {"source": "archaeological", "target": "考古学的"},
            {"source": "archaeologists", "target": "考古学家"},
            {"source": "arrogant", "target": "傲慢的"},
            {"source": "arthropods", "target": "节肢动物"},
            {"source": "Bos", "target": "牛属"},
            {"source": "base pair", "target": "碱基对"},
            {"source": "bacteria (bacterial cell)", "target": "细菌（细菌细胞）"},
            {"source": "barley", "target": "大麦"},
            {"source": "barriers to trade", "target": "贸易壁垒"},
            {"source": "beech", "target": "山毛榉"},
            {"source": "benefits of GM", "target": "转基因的好处"},
            {"source": "Bouldnor Cliff", "target": "博尔德纳悬崖"},
            {"source": "Bouldnor Cliff mud", "target": "博尔德纳悬崖泥浆"},
            {"source": "BSE", "target": "疯牛病"},
            {"source": "cancer-causing", "target": "致癌的"},
            {"source": "cancer-causing pieces", "target": "致癌片段"},
            {"source": "Canis", "target": "犬属"},
            {"source": "carbon emissions", "target": "碳排放"},
            {"source": "Cas enzyme", "target": "Cas酶"},
            {"source": "cattle", "target": "牛"},
            {"source": "centric origin", "target": "中心起源"},
            {"source": "chloroplast DNA", "target": "叶绿体DNA"},
            {"source": "chloroplasts (miniature factories)", "target": "叶绿体（微型工厂）"},
            {"source": "chromosome", "target": "染色体"},
            {"source": "civilized values", "target": "文明价值观"},
            {"source": "computer simulations", "target": "计算机模拟"},
            {"source": "conference", "target": "会议"},
            {"source": "core area paradigm", "target": "核心区域范式"},
            {"source": "corporation", "target": "公司"},
            {"source": "CRISPR (Clustered Regularly Interspaced Short Palindromic Repeats)", "target": "CRISPR（成簇规律间隔短回文重复序列）"},
            {"source": "cereal", "target": "谷物"},
            {"source": "consumers", "target": "消费者"},
            {"source": "data", "target": "数据"},
            {"source": "damaged DNA", "target": "受损DNA"},
            {"source": "de facto moratorium", "target": "事实上的暂停令"},
            {"source": "deer", "target": "鹿"},
            {"source": "defense mechanism", "target": "防御机制"},
            {"source": "Deutsche Bank", "target": "德意志银行"},
            {"source": "Deutsche Bank analysts", "target": "德意志银行分析师"},
            {"source": "dialogue", "target": "对话"},
            {"source": "diseases", "target": "疾病"},
            {"source": "DNA (deoxyribonucleic acid)", "target": "DNA（脱氧核糖核酸）"},
            {"source": "DNA sequences", "target": "DNA序列"},
            {"source": "DNA repair", "target": "DNA修复"},
            {"source": "DNA-cutting enzyme (molecular pair of scissors)", "target": "DNA切割酶（分子剪刀）"},
            {"source": "domesticated barley", "target": "栽培大麦"},
            {"source": "domesticated strains", "target": "驯化品系"},
            {"source": "domestication (agricultural domestication, domesticates)", "target": "驯化（农业驯化，驯化物种）"},
            {"source": "embryological development", "target": "胚胎发育"},
            {"source": "fast, local origin", "target": "快速本地起源"},
            {"source": "faux apology", "target": "虚假道歉"},
            {"source": "farmers", "target": "农民"},
            {"source": "fight it off", "target": "抵御它"},
            {"source": "founder effect", "target": "奠基者效应"},
            {"source": "grouse", "target": "松鸡"},
            {"source": "GM food", "target": "转基因食品"},
            {"source": "GMO (transgenic crops)", "target": "转基因生物（转基因作物）"},
            {"source": "grasses", "target": "草类"},
            {"source": "grubby", "target": "肮脏的"},
            {"source": "guide RNA (RNA guide)", "target": "引导RNA（RNA向导）"},
            {"source": "hazelnut shells", "target": "榛子壳"},
            {"source": "herbs", "target": "草本植物"},
            {"source": "herbicide", "target": "除草剂"},
            {"source": "herbicide tolerance", "target": "除草剂耐受性"},
            {"source": "heredity", "target": "遗传"},
            {"source": "homed in and locked onto", "target": "精准定位并结合"},
            {"source": "homes in", "target": "定位"},
            {"source": "homelands", "target": "发源地"},
            {"source": "homologous recombination", "target": "同源重组"},
            {"source": "human cells", "target": "人类细胞"},
            {"source": "humans", "target": "人类"},
            {"source": "hypothesis", "target": "假设"},
            {"source": "hypothesis-driven research", "target": "假设驱动型研究"},
            {"source": "inauspicious launch", "target": "不吉利的推出"},
            {"source": "immunity", "target": "免疫力"},
            {"source": "in-house scientists", "target": "内部科学家"},
            {"source": "invading pathogen", "target": "入侵病原体"},
            {"source": "internet", "target": "互联网"},
            {"source": "interbreeding", "target": "杂交"},
            {"source": "Jordan Valley", "target": "约旦河谷"},
            {"source": "knockout embryo", "target": "基因敲除胚胎"},
            {"source": "lab", "target": "实验室"},
            {"source": "locks onto", "target": "锁定"},
            {"source": "loss of seed dispersal", "target": "种子传播能力丧失"},
            {"source": "microscopy", "target": "显微技术"},
            {"source": "microscopy", "target": "显微技术"},
            {"source": "molecular ghosts", "target": "分子幽灵"},
            {"source": "molecular machinery", "target": "分子机制"},
            {"source": "mosaic ancestry", "target": "镶嵌祖先"},
            {"source": "mutation", "target": "突变"},
            {"source": "natural world", "target": "自然世界"},
            {"source": "nucleotide", "target": "核苷酸"},
            {"source": "nucleotide letter", "target": "核苷酸字母"},
            {"source": "oak", "target": "橡树"},
            {"source": "palindromic repeats", "target": "回文重复序列"},
            {"source": "pathogen", "target": "病原体"},
            {"source": "patterns", "target": "模式"},
            {"source": "Peter Melchett", "target": "彼得·梅尔切特"},
            {"source": "photosynthesis", "target": "光合作用"},
            {"source": "plant", "target": "植物"},
            {"source": "poplar", "target": "杨树"},
            {"source": "potential applications", "target": "潜在应用"},
            {"source": "practice", "target": "实践"},
            {"source": "prehistoric environment", "target": "史前环境"},
            {"source": "precise cut", "target": "精确切割"},
            {"source": "public", "target": "公众"},
            {"source": "public health", "target": "公共健康"},
            {"source": "public mistrust", "target": "公众不信任"},
            {"source": "rhetoric", "target": "言辞"},
            {"source": "researchers", "target": "研究人员"},
            {"source": "resist", "target": "抵制"},
            {"source": "repeating sections", "target": "重复片段"},
            {"source": "RNA (ribonucleic acid)", "target": "RNA（核糖核酸）"},
            {"source": "RNA guide", "target": "RNA引导链"},
            {"source": "rodent bones", "target": "啮齿动物骨骼"},
            {"source": "rodents", "target": "啮齿动物"},
            {"source": "Robb Fraley", "target": "罗布·弗雷利"},
            {"source": "scientific evidence", "target": "科学证据"},
            {"source": "scientific method", "target": "科学方法"},
            {"source": "sediment", "target": "沉积物"},
            {"source": "sell more herbicide", "target": "销售更多除草剂"},
            {"source": "sequencing", "target": "测序"},
            {"source": "sequencing net", "target": "测序网络"},
            {"source": "shared mutations", "target": "共享突变"},
            {"source": "Shapiro", "target": "夏皮罗"},
            {"source": "sieving", "target": "筛分"},
            {"source": "single base pair", "target": "单个碱基对"},
            {"source": "single origin", "target": "单一起源"},
            {"source": "single-origin paradigm", "target": "单一起源范式"},
            {"source": "single-nucleotide editing", "target": "单核苷酸编辑"},
            {"source": "soil erosion", "target": "土壤侵蚀"},
            {"source": "Solent", "target": "索伦特"},
            {"source": "Solent forest", "target": "索伦特森林"},
            {"source": "sow", "target": "播种"},
            {"source": "spacer sequences", "target": "间隔序列"},
            {"source": "species", "target": "物种"},
            {"source": "spinach", "target": "菠菜"},
            {"source": "splice", "target": "拼接"},
            {"source": "splice in another", "target": "插入另一段"},
            {"source": "stark", "target": "鲜明的"},
            {"source": "story", "target": "故事"},
            {"source": "supermarkets", "target": "超市"},
            {"source": "techniques", "target": "技术"},
            {"source": "technology", "target": "技术"},
            {"source": "technology itself", "target": "技术本身"},
            {"source": "therapeutically", "target": "治疗性地"},
            {"source": "tiny clues", "target": "微小线索"},
            {"source": "top in-house scientist", "target": "首席内部科学家"},
            {"source": "untrustworthy", "target": "不可靠的"},
            {"source": "vertebrates", "target": "脊椎动物"},
            {"source": "viral attack", "target": "病毒感染"},
            {"source": "viral DNA", "target": "病毒DNA"},
            {"source": "viruses", "target": "病毒"},
            {"source": "water use", "target": "用水量"},
            {"source": "weed killer", "target": "除草剂"},
            {"source": "weed killer-resistant soybean", "target": "抗除草剂大豆"},
            {"source": "wild ancestor", "target": "野生祖先"},
            {"source": "wild barley", "target": "野生大麦"},
            {"source": "wild relatives", "target": "野生近缘种"},
            {"source": "wolves", "target": "狼"},
            {"source": "Zagros Mountains", "target": "扎格罗斯山脉"},
        ],
    }


def translate_text(text: str, translation_options: Dict, 
                   min_delay: float = 3.0, max_delay: float = 6.0) -> str:
    """
    使用DashScope API翻译单个文本段
    
    该函数向DashScope API发送翻译请求，并返回翻译结果。如果翻译失败，则返回原文。
    本函数还提供了详细的进度和性能信息，方便用户了解翻译过程。
    包含频率控制和重试机制，防止API限流。
    
    Args:
        text (str): 要翻译的文本
        translation_options (dict): 翻译配置选项
        min_delay (float): 最小延迟时间（秒）
        max_delay (float): 最大延迟时间（秒）
        
    Returns:
        str: 翻译后的文本，如果翻译失败则返回原文
        
    Raises:
        ValueError: 当环境变量DASHSCOPE_API_KEY未设置时抛出
    """
    # 从环境变量获取API密钥，确保安全性，避免硬编码密钥
    api_key = os.getenv('DASHSCOPE_API_KEY')
    if not api_key:
        logger.error("DASHSCOPE_API_KEY 环境变量未设置")
        raise ValueError("DASHSCOPE_API_KEY 环境变量未设置")
    
    # 输出正在翻译的文本前5000个字符以供调试（避免输出过长的文本）
    logger.info(f"正在翻译文本: {text[:5000]}{'...' if len(text) > 5000 else ''}")
    
    # 在请求前添加随机延迟，避免API限流
    delay = random.uniform(min_delay, max_delay)
    logger.info(f"请求前延迟 {delay:.2f} 秒")
    time.sleep(delay)
    
    # 开始计时，用于性能监控
    start_time = time.time()
    
    # 重试机制
    max_retries = 10
    retry_count = 0
    
    while retry_count < max_retries:
        try:
            # 调用DashScope翻译API，使用qwen-mt-turbo模型
            response = dashscope.Generation.call(
                api_key=api_key,
                model="qwen-mt-turbo",
                messages=[{"role": "user", "content": text}],
                result_format='message',
                translation_options=translation_options
            )
            
            # 结束计时并输出耗时
            end_time = time.time()
            logger.info(f"翻译耗时: {end_time - start_time:.2f}秒")
            
            # 检查API响应状态
            if response.status_code == 200:
                # 输出翻译结果的前5000个字符以供调试
                translated_text = response.output.choices[0].message.content
                logger.info(f"翻译成功: {translated_text[:5000]}{'...' if len(translated_text) > 5000 else ''}")
                return translated_text
            elif response.status_code == 429:  # 请求过于频繁
                retry_count += 1
                logger.warning(f"API限流，正在重试 ({retry_count}/{max_retries})...")
                # 指数退避策略
                backoff_delay = min(60, (2 ** retry_count) + random.uniform(0, 1))  # 最大延迟60秒
                time.sleep(backoff_delay)
            else:
                # 输出API错误信息
                logger.error(f"翻译失败: {response.code} - {response.message}")
                if response.code == 429:  # 如果是限流错误，继续重试
                    retry_count += 1
                    backoff_delay = min(60, (2 ** retry_count) + random.uniform(0, 1))
                    time.sleep(backoff_delay)
                else:
                    return text  # 非限流错误则直接返回原文
        except Exception as e:
            # 捕获并处理翻译请求中的异常，确保程序稳定性
            logger.error(f"翻译请求出错: {e}")
            retry_count += 1
            if retry_count < max_retries:
                backoff_delay = min(60, (2 ** retry_count) + random.uniform(0, 1))
                logger.info(f"等待 {backoff_delay:.2f} 秒后重试 ({retry_count}/{max_retries})...")
                time.sleep(backoff_delay)
            else:
                logger.error(f"重试{max_retries}次后仍失败，返回原文")
    
    return text  # 重试失败后返回原文


def translate_html_content(html_content: str, translation_options: Dict,
                          min_delay: float = 3.0, max_delay: float = 6.0) -> str:
    """
    翻译HTML内容中的文本节点，保持HTML结构不变
    
    该函数使用BeautifulSoup解析HTML，找到所有可翻译的文本节点（排除script、style、title等标签内的文本），
    然后对每个文本节点调用translate_text函数进行翻译，并保持原有的HTML结构。
    
    Args:
        html_content (str): HTML内容
        translation_options (dict): 翻译配置选项
        min_delay (float): 最小延迟时间（秒）
        max_delay (float): 最大延迟时间（秒）
        
    Returns:
        str: 翻译后的HTML内容，保持原有结构
    """
    # 使用BeautifulSoup解析HTML，确保正确处理HTML结构，使用'html.parser'作为解析器
    soup = BeautifulSoup(html_content, 'html.parser')
    
    # 找到所有文本节点进行翻译
    text_elements = []
    for element in soup.find_all(string=True):  # 使用string=True查找所有文本节点
        # 只翻译纯文本节点（NavigableString对象）
        if isinstance(element, NavigableString):
            # 检查父标签是否为不应翻译的标签（如script、style、title）
            parent = element.parent
            if parent.name not in ['script', 'style', 'title', 'meta', 'link']:
                # 只处理非空文本内容（去掉首尾空格后长度大于0）
                stripped_content = str(element).strip()
                if stripped_content:  # 如果文本内容非空
                    text_elements.append(element)
    
    # 输出找到的文本节点数量，提供进度反馈
    logger.info(f"共找到 {len(text_elements)} 个需要翻译的文本节点")
    
    # 循环处理每个文本节点
    for idx, element in enumerate(text_elements, 1):
        logger.info(f"正在处理第 {idx}/{len(text_elements)} 个文本节点...")
        original_text = str(element).strip()
        translated_text = translate_text(original_text, translation_options, min_delay, max_delay)
        # 使用replace_with方法替换原始文本节点，保持HTML结构完整性
        element.replace_with(translated_text)
    
    # 返回修改后的HTML字符串，保持所有原有的HTML标签和结构
    return str(soup)


def parse_args():
    """
    解析命令行参数
    
    定义和解析脚本的命令行参数，目前只需要输入文件路径参数。
    
    Returns:
        argparse.Namespace: 解析后的参数对象
    """
    parser = argparse.ArgumentParser(
        description='HTML文件翻译脚本',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
用法示例:
  python transhtml.py -i input.html
        """
    )
    parser.add_argument('-i', '--input', required=True, help='输入的HTML文件路径')
    parser.add_argument('-m', '--max-size', type=int, default=10*1024*1024, 
                        help='最大允许处理的文件大小（字节），默认为10MB')
    parser.add_argument('--min-delay', type=float, default=3.0, 
                        help='API请求间的最小延迟时间（秒），默认为3.0秒')
    parser.add_argument('--max-delay', type=float, default=6.0, 
                        help='API请求间的最大延迟时间（秒），默认为6.0秒')
    return parser.parse_args()


def validate_file_size(file_path: str, max_size: int) -> bool:
    """
    验证文件大小是否在允许范围内
    
    Args:
        file_path (str): 文件路径
        max_size (int): 最大允许的文件大小（字节）
        
    Returns:
        bool: 如果文件大小在允许范围内则返回True，否则返回False
    """
    file_size = os.path.getsize(file_path)
    if file_size > max_size:
        logger.error(f"文件 {file_path} 大小为 {file_size} 字节，超过允许的最大大小 {max_size} 字节")
        return False
    return True


def main():
    """
    主函数 - 程序入口点
    
    该函数负责程序的完整执行流程：
    1. 解析命令行参数
    2. 验证输入文件存在性及大小
    3. 读取HTML内容
    4. 执行翻译处理
    5. 保存翻译结果
    6. 处理异常情况
    """
    logger.info("=== HTML文件翻译工具（生产版本，带频率控制）===")
    
    # 解析命令行参数
    args = parse_args()
    
    # 检查输入文件是否存在，确保文件路径有效
    if not os.path.exists(args.input):
        logger.error(f"错误: 输入文件 {args.input} 不存在")
        sys.exit(1)
    
    # 验证文件大小
    if not validate_file_size(args.input, args.max_size):
        logger.error("文件过大，无法处理")
        sys.exit(1)
    
    # 获取输入文件大小，提供给用户参考
    file_size = os.path.getsize(args.input)
    logger.info(f"输入文件: {args.input} (大小: {file_size} 字节)")
    
    # 生成输出文件名，格式为 input-trans.html
    base_name, ext = os.path.splitext(args.input)
    output_file = f"{base_name}-trans{ext}"
    
    # 获取翻译配置
    translation_options = get_translation_options()
    
    try:
        # 读取输入文件
        logger.info("正在读取输入文件...")
        with open(args.input, 'r', encoding='utf-8') as f:
            content = f.read()
        
        logger.info("开始翻译HTML内容...")
        start_time = time.time()
        
        # 翻译HTML内容，保持结构不变
        translated_content = translate_html_content(
            content, 
            translation_options,
            min_delay=args.min_delay,
            max_delay=args.max_delay
        )
        
        end_time = time.time()
        logger.info(f"翻译总耗时: {end_time - start_time:.2f}秒")
        
        # 写入输出文件
        logger.info("正在写入输出文件...")
        with open(output_file, 'w', encoding='utf-8') as f:
            f.write(translated_content)
        
        # 获取输出文件大小，提供给用户参考
        output_file_size = os.path.getsize(output_file)
        logger.info(f"输出文件: {output_file} (大小: {output_file_size} 字节)")
        
        logger.info("翻译完成！")
        
    except Exception as e:
        # 捕获并处理文件操作中的异常，提供错误信息并安全退出
        logger.error(f"处理文件时出错: {e}")
        sys.exit(1)


if __name__ == "__main__":
    # 只有直接运行脚本时才执行主函数，确保模块可被安全导入
    main()
